import {
    assign,
    forEach,
    isArray,
    every
  } from 'min-dash';
  
  import {
    is
  } from 'bpmn-js/lib/util/ModelUtil';
  
  import {
    isExpanded,
    isEventSubProcess
  } from 'bpmn-js/lib/util/DiUtil';
  
  import {
    isAny
  } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
  
  import {
    getChildLanes
  } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
  
  import {
    hasPrimaryModifier
  } from 'diagram-js/lib/util/Mouse';
  
  
  /**
   * A provider for BPMN 2.0 elements context pad
   */
  export default function ContextPadProvider(
    config, injector, eventBus,
    contextPad, modeling, elementFactory,
    connect, create, popupMenu,
    canvas, rules, translate) {
  
    config = config || {};
  
    contextPad.registerProvider(this);
  
    this._contextPad = contextPad;
  
    this._modeling = modeling;
  
    this._elementFactory = elementFactory;
    this._connect = connect;
    this._create = create;
    this._popupMenu = popupMenu;
    this._canvas = canvas;
    this._rules = rules;
    this._translate = translate;
  
    if (config.autoPlace !== false) {
      this._autoPlace = injector.get('autoPlace', false);
    }
  
    eventBus.on('create.end', 250, function(event) {
      var context = event.context,
        shape = context.shape;
  
      if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
        return;
      }
  
      var entries = contextPad.getEntries(shape);
  
      if (entries.replace) {
        entries.replace.action.click(event, shape);
      }
    });
  }
  
  ContextPadProvider.$inject = [
    'config.contextPad',
    'injector',
    'eventBus',
    'contextPad',
    'modeling',
    'elementFactory',
    'connect',
    'create',
    'popupMenu',
    'canvas',
    'rules',
    'translate'
  ];
  
  ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
    var modeling = this._modeling;
  
    var actions = {};
  
    if (this._isDeleteAllowed(elements)) {
      assign(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: this._translate('Remove'),
          action: {
            click: function(event, elements) {
              modeling.removeElements(elements.slice());
            }
          }
        }
      });
    }
  
    return actions;
  };
  
  /**
   * @param {djs.model.Base[]} elements
   * @return {boolean}
   */
  ContextPadProvider.prototype._isDeleteAllowed = function(elements) {
  
    var baseAllowed = this._rules.allowed('elements.delete', {
      elements: elements
    });
  
    if (isArray(baseAllowed)) {
      return every(baseAllowed, function(element) {
        return includes(baseAllowed, element);
      });
    }
  
    return baseAllowed;
  };
  
  ContextPadProvider.prototype.getContextPadEntries = function(element) {
    var contextPad = this._contextPad,
      modeling = this._modeling,
  
      elementFactory = this._elementFactory,
      connect = this._connect,
      create = this._create,
      popupMenu = this._popupMenu,
      rules = this._rules,
      autoPlace = this._autoPlace,
      translate = this._translate;
  
    var actions = {};
  
    if (element.type === 'label') {
      return actions;
    }
  
    var businessObject = element.businessObject;
  
    function startConnect(event, element) {
      connect.start(event, element);
    }
  
    function removeElement(e, element) {
      modeling.removeElements([ element ]);
    }
  
    function getReplaceMenuPosition(element) {
  
      var Y_OFFSET = 5;
  
      var pad = contextPad.getPad(element).html;
  
      var padRect = pad.getBoundingClientRect();
  
      var pos = {
        x: padRect.left,
        y: padRect.bottom + Y_OFFSET
      };
  
      return pos;
    }
  
  
    /**
     * Create an append action
     *
     * @param {string} type
     * @param {string} className
     * @param {string} [title]
     * @param {Object} [options]
     *
     * @return {Object} descriptor
     */
    function appendAction(type, className, title, options) {
  
      if (typeof title !== 'string') {
        options = title;
        title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
      }
  
      function appendStart(event, element) {
  
        var shape = elementFactory.createShape(assign({ type: type }, options));
        create.start(event, shape, {
          source: element
        });
      }
  
  
      var append = autoPlace ? function(event, element) {
        var shape = elementFactory.createShape(assign({ type: type }, options));
  
        autoPlace.append(element, shape);
      } : appendStart;
  
  
      return {
        group: 'model',
        className: className,
        title: title,
        action: {
          dragstart: appendStart,
          click: append
        }
      };
    }
  
    function splitLaneHandler(count) {
  
      return function(event, element) {
  
        // actual split
        modeling.splitLane(element, count);
  
        // refresh context pad after split to
        // get rid of split icons
        contextPad.open(element, true);
      };
    }
  
  
    if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {
  
      var childLanes = getChildLanes(element);
  
      assign(actions, {
        'lane-insert-above': {
          group: 'lane-insert-above',
          className: 'bpmn-icon-lane-insert-above',
          title: translate('Add Lane above'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'top');
            }
          }
        }
      });
  
      if (childLanes.length < 2) {
  
        if (element.height >= 120) {
          assign(actions, {
            'lane-divide-two': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-two',
              title: translate('Divide into two Lanes'),
              action: {
                click: splitLaneHandler(2)
              }
            }
          });
        }
  
        if (element.height >= 180) {
          assign(actions, {
            'lane-divide-three': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-three',
              title: translate('Divide into three Lanes'),
              action: {
                click: splitLaneHandler(3)
              }
            }
          });
        }
      }
  
      assign(actions, {
        'lane-insert-below': {
          group: 'lane-insert-below',
          className: 'bpmn-icon-lane-insert-below',
          title: translate('Add Lane below'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'bottom');
            }
          }
        }
      });
  
    }
  
    if (is(businessObject, 'bpmn:FlowNode')) {
  
      if (is(businessObject, 'bpmn:EventBasedGateway')) {
  
        assign(actions, {
          'append.receive-task': appendAction(
            'bpmn:ReceiveTask',
            'bpmn-icon-receive-task',
            translate('Append ReceiveTask')
          ),
          'append.message-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-message',
            translate('Append MessageIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:MessageEventDefinition' }
          ),
          'append.timer-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-timer',
            translate('Append TimerIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:TimerEventDefinition' }
          ),
          'append.condition-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-condition',
            translate('Append ConditionIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
          ),
          'append.signal-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-signal',
            translate('Append SignalIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:SignalEventDefinition' }
          )
        });
      } else
  
      if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
  
        assign(actions, {
          'append.compensation-activity':
            appendAction(
              'bpmn:Task',
              'bpmn-icon-task',
              translate('Append compensation activity'),
              {
                isForCompensation: true
              }
            )
        });
      } else
  
      if (!is(businessObject, 'bpmn:EndEvent') &&
        !businessObject.isForCompensation &&
        !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
        !isEventSubProcess(businessObject)) {
  
        assign(actions, {
          'append.end-event': appendAction(
            'bpmn:EndEvent',
            'bpmn-icon-end-event-none',
            translate('Append EndEvent')
          ),
          'append.gateway': appendAction(
            'bpmn:ExclusiveGateway',
            'bpmn-icon-gateway-none',
            translate('Append Gateway')
          ),
          'append.append-user-task': appendAction(
            'bpmn:Task',
            'bpmn-icon-user-task',
            '添加用户任务'  
          ),
          'append.intermediate-event': appendAction(
            'bpmn:IntermediateThrowEvent',
            'bpmn-icon-intermediate-event-none',
            translate('Append Intermediate/Boundary Event')
          )
        });
      }
    }
  
    if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
  
      // Replace menu entry
      assign(actions, {
        'replace': {
          group: 'edit',
          className: 'bpmn-icon-screw-wrench',
          title: translate('Change type'),
          action: {
            click: function(event, element) {
  
              var position = assign(getReplaceMenuPosition(element), {
                cursor: { x: event.x, y: event.y }
              });
  
              popupMenu.open(element, 'bpmn-replace', position, {
                title: translate('Change element'),
                width: 300,
                search: true
              });
            }
          }
        }
      });
    }
  
    if (is(businessObject, 'bpmn:SequenceFlow')) {
      assign(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation'
        )
      });
    }
  
    if (
      isAny(businessObject, [
        'bpmn:FlowNode',
        'bpmn:InteractionNode',
        'bpmn:DataObjectReference',
        'bpmn:DataStoreReference',
      ])
    ) {
      assign(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation'
        ),
  
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate(
            'Connect using ' +
            (businessObject.isForCompensation
              ? ''
              : 'Sequence/MessageFlow or ') +
            'Association'
          ),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }
  
    if (is(businessObject, 'bpmn:TextAnnotation')) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using Association'),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }
  
    if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using DataInputAssociation'),
          action: {
            click: startConnect,
            dragstart: startConnect
          }
        }
      });
    }
  
    if (is(businessObject, 'bpmn:Group')) {
      assign(actions, {
        'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
      });
    }
  
    // delete element entry, only show if allowed by rules
    var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
  
    if (isArray(deleteAllowed)) {
  
      // was the element returned as a deletion candidate?
      deleteAllowed = deleteAllowed[0] === element;
    }
  
    if (deleteAllowed) {
      assign(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: translate('Remove'),
          action: {
            click: removeElement
          }
        }
      });
    }
  
    return actions;
  };
  
  
  // helpers /////////
  
  function isEventType(eventBo, type, definition) {
  
    var isType = eventBo.$instanceOf(type);
    var isDefinition = false;
  
    var definitions = eventBo.eventDefinitions || [];
    forEach(definitions, function(def) {
      if (def.$type === definition) {
        isDefinition = true;
      }
    });
  
    return isType && isDefinition;
  }
  
  function includes(array, item) {
    return array.indexOf(item) !== -1;
  }
  